/*
 ===============================================================================
 Name        : main.c
 Author      : 
 Version     :
 Copyright   : Copyright (C)
 Description : main definition
 ===============================================================================
 */

#include "LPC13xx.h"
#include <cr_section_macros.h>
#include <string.h>

#include "uart.h"

#include "usb_rom_drivers.h"
#include "usbdesc.h"
#include "usb.h"
#include "pwm.h"
#include "iap.h"

typedef struct __attribute__ (( __packed__ )) {
	uint8_t R;
	uint8_t G;
	uint8_t B;
} RGB_t;

typedef struct __attribute__ (( __packed__ )) {
	uint8_t strip_type; //typ taśmy RGB
	uint32_t length; //dlugosc tasmy
	RGB_t start_color; //początkowy kolor świecenia
} stripSettings_t;

typedef struct __attribute__ (( __packed__ )) {
	uint32_t version;
	uint32_t length; //dlugosc tasmy
	RGB_t zwykla_tasma; //kolor na wyjsciach RGB
	RGB_t diodki[500];
	stripSettings_t settings;
} REG_t;

volatile REG_t shared_memory;

USB_DEV_INFO DeviceInfo;
HID_DEVICE_INFO HidDevInfo;
ROM ** rom = (ROM **) 0x1fff1ff8;

typedef struct {

	unsigned char data[64];
	unsigned char valid;
	unsigned char lenght;

} USBBUFF_t;
volatile USBBUFF_t usb_outbuf;

void GetInReport(uint8_t src[], uint32_t length) {
	if (usb_outbuf.valid == 1) {
		src[0] = 2;
		memcpy(&src[1], (void *) usb_outbuf.data, usb_outbuf.lenght);
		usb_outbuf.valid = 0;
		usb_outbuf.lenght = 0;
		return;
	} else {
		src[0] = 0;
	}
}

/*
 *  Set HID Output Report <- OutReport
 */
void SetOutReport(uint8_t dst[], uint32_t length) {
	//Tryb do debugowania
	if (dst[0] == 1) {

	}

	//Adresowalny dostęp pamięci (z potwierdzeniem)
	if (dst[0] == 2) {

		uint32_t addr = dst[1] | (dst[2] << 8) | (dst[3] << 16)
				| (dst[4] << 24);
		uint8_t read = dst[5] & (1 << 7);
		uint8_t size = dst[5] & 0x7F;
		if ((addr + size) > sizeof(shared_memory))
			size = 0;

		if ((length - 6) < size)
			return; //blad rozmiaru

		//Odczyt danych z pamięci
		if (read) {
			if (size > 58)
				return; //blad rozmiaru
			dst[5] &= ~(1 << 7);
			memcpy((void *) &usb_outbuf.data[0], &dst[1], 5);
			memcpy((void *) &usb_outbuf.data[5],
					(uint8_t *) &shared_memory + addr, size);
			usb_outbuf.valid = 1;
			usb_outbuf.lenght = size + 5;
		}

		//zapis danych do pamieci
		else {
			//Zapisanie danych
			memcpy((uint8_t *) &shared_memory + addr, &dst[6], size);

			//jezeli dane się zmieniły to zapisz we flashu
			if (memcmp((void *) &shared_memory.settings, (void *) 0x7000,
					sizeof(stripSettings_t))) {
				write_flash((void *) 0x7000, (void *) &shared_memory.settings,
						sizeof(stripSettings_t));
			}
			//przygotowanie odpowiedzi potwierdzenia
			//dst[5] |= (1<<7);
			//memcpy( (void *)usb_outbuf.data, &dst[1], size+5);
			//usb_outbuf.valid = 1;
			//usb_outbuf.lenght = size+5;

		}
	}

	//bootloader
	if (dst[0] == 0xA5) {
		LPC_PMU->GPREG0 = 0x5a5a5a5a;
		NVIC_SystemReset();
	}
}

void initGPIO() {
	//USB Connect
	LPC_IOCON->PIO0_6 |= (1 << 0);

	//DATA (UART - TX)
	//LPC_IOCON->PIO1_7 &= ~(3<<3);

	//SCK i SCK_NEG = 0
	LPC_GPIO0->MASKED_ACCESS[(3 << 10)] = 0;

	//negacja DATA
	LPC_GPIO0->DIR |= (1 << 9);
	LPC_GPIO0->MASKED_ACCESS[(1 << 9)] = (1 << 9);
}

void initUSB() {
	volatile uint32_t i;
	HidDevInfo.idVendor = 0x1FC9;
	HidDevInfo.idProduct = 0x0003;
	HidDevInfo.bcdDevice = 0x0100;
	HidDevInfo.StrDescPtr = (uint32_t) &USB_StringDescriptor[0];
	HidDevInfo.InReportCount = 64;
	HidDevInfo.OutReportCount = 64;
	HidDevInfo.SampleInterval = 0x01;
	HidDevInfo.InReport = GetInReport;
	HidDevInfo.OutReport = SetOutReport;

	DeviceInfo.DevType = USB_DEVICE_CLASS_HUMAN_INTERFACE;
	DeviceInfo.DevDetailPtr = (uint32_t) &HidDevInfo;

#define     EN_TIMER32_1    (1<<10)
#define     EN_IOCON        (1<<16)
#define     EN_USBREG       (1<<14)
	/* Enable Timer32_1, IOCON, and USB blocks (for USB ROM driver) */LPC_SYSCON->SYSAHBCLKCTRL
			|= (EN_TIMER32_1 | EN_IOCON | EN_USBREG);

	/* Use pll and pin init function in rom */
	//(*rom)->pUSBD->init_clk_pins();
	USBIOClkConfig();

	/* insert a delay between clk init and usb init */
	for (i = 0; i < 75; i++) {
	}

	(*rom)->pUSBD->init(&DeviceInfo); /* USB Initialization */

	(*rom)->pUSBD->connect(1); /* USB Connect */
}

int main(void) {
	volatile static int i = 0;

	//odczyt ustawien z flasha
	memcpy((void *) &shared_memory.settings, (void *) 0x7000,
			sizeof(stripSettings_t));

	//domyslny kolor diodek
	for (i = 0; i < 500; i++) {
		shared_memory.diodki[i].R = shared_memory.settings.start_color.R;
		shared_memory.diodki[i].G = shared_memory.settings.start_color.G;
		shared_memory.diodki[i].B = shared_memory.settings.start_color.B;
	}

	shared_memory.zwykla_tasma.R = shared_memory.settings.start_color.R;
	shared_memory.zwykla_tasma.G = shared_memory.settings.start_color.G;
	shared_memory.zwykla_tasma.B = shared_memory.settings.start_color.B;

	//wersja 1.1
	shared_memory.version = 11;
	shared_memory.length = shared_memory.settings.length > 500 ? 50
			: shared_memory.settings.length; //domyślna długość (50 = 1 taśma)

	initGPIO();

	PWM_INIT();
	PWM(shared_memory.zwykla_tasma.R,shared_memory.zwykla_tasma.G,shared_memory.zwykla_tasma.B);

	initUSB();

	UARTInit();

	while (1) {

		//wyłącz przerwanie od USB na czas nadawania
		NVIC_DisableIRQ(USB_IRQn);

		PWM(shared_memory.zwykla_tasma.R,shared_memory.zwykla_tasma.G,shared_memory.zwykla_tasma.B);

		//wylij wartości do taśmy
		for (i = 0; i < shared_memory.length && i < 500; i++) {
			uint32_t j;
			//R&&
			for (j = 0; j < 8; j++) {
				if (shared_memory.diodki[i].R & (1 << (7 - j)))
					UARTSendByte(0x80);
				else
					UARTSendByte(0xFE);
			}
			//B
			for (j = 0; j < 8; j++) {
				if (shared_memory.diodki[i].B & (1 << (7 - j)))
					UARTSendByte(0x80);
				else
					UARTSendByte(0xFE);
			}

			//G
			for (j = 0; j < 8; j++) {
				if (shared_memory.diodki[i].G & (1 << (7 - j)))
					UARTSendByte(0x80);
				else
					UARTSendByte(0xFE);
			}
		}
		//włącz przerwanie od USB

		NVIC_EnableIRQ(USB_IRQn);

		//todo zrobić na timerze
		//delay
		for (i = 0; i < 1000; i++) {
		}

	}
	return 0;
}

void USB_IRQHandler() {
	(*rom)->pUSBD->isr();
}
